package com.dongnao.network.netty_packet;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * TCP 粘包和拆包
 * <p>
 * 面向流的通信是无消息保护边界的。由于TCP无消息保护边界，需要在接收端处理消息边界问题。
 * <p>
 * 假设客户端分别发送了两个数据包D1和D2给服务端，由于服务端一次读取到字节数是不确定的，故可能存在以下四种情况:
 * 1)服务端分两次读取到了两个独立的数据包，分别是D1和 D2，没有粘包和拆包
 * 2)服务端一次接受到了两个数据包，D1和D2粘合在一起，称之为TCP粘包
 * 3)服务端分两次读取到了数据包，第一次读取到了完整的D1包和D2包的部分内容，第二次读取到了D2包的剩余内容，这称之为TCP拆包。
 * 4)服务端分两次读取到了数据包，第一次读取到了D1包的部分内容D1_1，第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。
 * <p>
 * 解决方案
 * 1)使用自定义协议＋编解码器来解决
 * 2)关键就是要解决服务器端每次读取数据长度的问题，这个问题解决，就不会出现服务器多读或少读数据的问题，从而避免的TCP粘包、拆包。
 */
public class NettyServer {

    public static void main(String[] args) {

        //创建BossGroup 和WorkerGroup
        //1.创建两个线程组bossGroup和 workerGroup
        //2.bossGroup只是处理连接请求，真正的和客户端业务处理，会交给 workerGroup完成
        //3．两个都是无限循环
        //4.bossGroup和 workerGroup含有的子线程(NioEventLoop)的个数默认为cpu核数*2
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();//不指定的话个数默认为cpu核数*2

        try {
            //ServerBootStrap本质上是一个容器，放置各种网络服务配置，然后组合起来
            ServerBootstrap bootstrap = new ServerBootstrap();

            //链式编程设置配置信息
            bootstrap.group(bossGroup, workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//使用NioServerSocketChannel作为服务器通道的实现
                    .option(ChannelOption.SO_BACKLOG, 128)//设置线程队列的连接个数//设置服务端可连接队列大小
                    .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动的连接个数
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {//使用匿名方式创建一个通道测试对象
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("客户端通道 " + ch.hashCode());
                            //ChannelPipeline是个双向链表，把ChannelHandler包装成ChannelHandlerContext加入其中
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("decoder", new MyMessageDecoder());
                            pipeline.addLast("encoder", new MyMessageEncoder());
//                            ch.pipeline().addLast(new NettyServerHandler());
                            pipeline.addLast(new NettyServerHandler2());
                        }
                    });

            System.out.println("服务器开始启动..." + System.currentTimeMillis());
            //api是基于异步的事件驱动，需要做同步
            ChannelFuture cf = bootstrap.bind(8899).sync();

            //对关闭通道进行监听//等待服务端channel关闭
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
